from __future__ import division

import os
import math

import pygame

from settings import settings

from vectors import Vector, AngleVector, Segment
import angles
import collision

from objects import Object

class MouseTarget(object):
	angle = 0
	_remove = False
	
	def _getpos(self):
		return Vector(*pygame.mouse.get_pos())
	
	position = property(_getpos)
	
	speed = 0

MOUSE = MouseTarget()

class Damage(object):
	def __init__(self, position, radius, damage, splash=False):
		self.circle = collision.Circle(position, radius)
		self.rect = self.circle.rect()
		self.damage = damage
		self.splash = splash

	def apply(self, layer):
		for object in layer.objects_within_rect(self.rect):
			if self.circle.collidecircle(object.circle):
				object.health -= self.damage
				
				if not self.splash:
					break
					
class LineDamage(object):
	def __init__(self, game, start, end, damage, splash=True):
		self.game = game
		
		self.segment = Segment(start, end)
		self.rect = self.segment.rect(self.game.rect)
		self.damage = damage
		self.splash = splash
	
	def apply(self, layer):	
		for object in layer.objects_within_rect(self.rect):
			if Vector(object.circle.x, object.circle.y).distance_to_segment(self.segment) <= object.circle.radius:
				object.health -= self.damage
				
				if not self.splash:
					break

class Bullet(Object):
	render_layer = 0
	
	speed = 25
	
	collisions = None
	
	def __init__(self, game, position, destination):
		super(Bullet, self).__init__(position)
		
		self.game = game
		
		self.position = Vector(*position)
		self.prev_pos = self.position 
		self.destination = destination
		self.angle = position.angle_to_point(Vector(*destination))
		
		self.first = True
		self.last = False

	def update(self):
		if self.first:
			self.first = False
			return
		elif self.last:
			self.remove()
			return
		
		x, y = self.position
		dest_x, dest_y = self.destination
		
		if (x - dest_x)**2 + (y - dest_y)**2 < (self.speed)**2:
			self.prev_pos = self.position
			self.position = self.destination
			
			self.game.damages.add(Damage(self.position, 2, 0.4))
			self.last = True
		else:
			self.position += AngleVector(self.angle, self.speed)
	
	def draw(self, surface):
		pygame.draw.line(surface, (192, 192, 192), tuple(self.prev_pos), tuple(self.position))
		self.prev_pos = Vector(*self.position)

class Rail(Bullet):
	def __init__(self, game, start, end):
		super(Rail, self).__init__(game, start, end)
		
		self.start = Vector(*start)
		self.end = Vector(*end)
		
		self.timer = 5
		
		self.game.damages.add(LineDamage(self.game, self.start, self.end, 1))
	
	def update(self):
		if self.timer > 0:
			self.timer -= 1
			self.game.damages.add(LineDamage(self.game, self.start, self.end, 1))
		else:
			self.remove()
	
	def draw(self, surface):
		pygame.draw.line(surface, (128, 128, 255), tuple(self.start), tuple(self.end))
		

class Socket(Object):
	graphic = "turrets/socket.png"
	
	render_layer = 1
	
	collisions = None
	
	def __init__(self, game, position):
		super(Socket, self).__init__(game)
		
		self.position = Vector(*position)
		
		x, y = self.position
		self.rect = collision.Rect((x - 16, y - 16, 32, 32))
		self.circle = self.rect.circle()
		
		self._remove = False
		self.turret = None
		self.selected = False
	
	def update(self):
		self.selected = (self.turret in self.game.selected)
	
	def draw(self, surface):
		x, y = self.position
		width, height = self.graphic.get_size()
		surface.blit(self.graphic, (x - width // 2, y - height // 2))
		
		if self.selected:
			pygame.draw.circle(surface, (0, 0, 255), self.circle.position, self.circle.radius, 1)
			
	def attach(self, turret):
		self.turret = turret

class Turret(Object):
	graphic = "turrets/gatling_gun.png"
	
	render_layer = 2
	
	fire_rate = 2
	
	turn_speed = math.radians(5)
	turn_speed_weight = 10
	
	range = 250
	range_weight = 1
	
	bullet = Bullet
	
	def __init__(self, game, socket, angle=0):		
		super(Turret, self).__init__(game)
		
		self.socket = socket
		self.socket.attach(self)
		
		self.position = self.socket.position
		self.angle = angle
		
		x, y = self.position
		self.range_rect = collision.Rect(x - self.range, y - self.range, self.range * 2, self.range * 2)
		
		self.persistance = self.turn_speed * self.turn_speed_weight + self.range * self.range_weight
		
		self.target = 0
		self.target_locked = False
		
		self.fire_delay = 0
		
		self._selected = False
		self._remove = False
	
	def update(self):		
		if self.target:
			self._target()
			
			if self.target_locked:
				if self.fire_delay <= 0:
					self._shoot()
					self.fire_delay = self.fire_rate
				else:
					self.fire_delay -= 1
					
			if self.target._remove: self.target = None
	
	def selected(self):
		self._selected = True
		
	def deselected(self):
		self._selected = False
	
	def _target(self):
		self._turn_to_target(self.target.position)
	
	def _no_target(self):
		self.target_locked = False
	
	def draw(self, surface):
		x, y = self.position
		
		rotate = pygame.transform.rotozoom(self.graphic, math.degrees(-self.angle), 1)
		width, height = rotate.get_size()
	
		surface.blit(rotate, (x - width // 2, y - height // 2))
		
	def crosshair(self, surface):
		if self.target:
			position = self.target.position
			distance = self.position.distance(position)
			
			x, y = self.target.position
			
			if distance <= self.range:
				position = (int(x), int(y))
				radius = int(max(distance / 25, 1))
			
				pygame.draw.circle(surface, (255, 0, 0, 128), position, radius, 1)
			
			range_ratio = distance / self.range
			alpha = min(int(255 * range_ratio), 255)
			
			arc_range = math.radians(20)
			
			pygame.draw.arc(surface, (255, 0, 0, alpha), self.range_rect, -self.angle - arc_range, -self.angle + arc_range)
	
	def _shoot(self):
		target_position = self.target.position
		target_distance = self.position.distance(target_position)
		
		if target_distance <= self.range:
			position = self.position + AngleVector(self.angle, target_distance)
			bullet = self.bullet(self.game, self.position, angles.random_point_in_circle((position, target_distance / 25)))
			self.game.add_object(bullet)
	
	def _turn_to_target(self, position):
		target_angle = self.position.angle_to_point(position)
		direction = angles.direction_to_angle(self.angle, target_angle)
		shortest = min(angles.rotations_to_angle(self.angle, target_angle))
		
		if self.turn_speed > shortest:
			self.angle = target_angle					
		else:
			self.angle = angles.clamp_angle(self.angle + (self.turn_speed * direction)) 
		
		if self.angle - self.turn_speed <= target_angle <= self.angle + self.turn_speed: 
			self.target_locked = True
		else:
			self.target_locked = False
	
	def collisions(self):
		if not self.target:
			self._no_target()

class MouseTurret(Turret):
	def selected(self):
		super(MouseTurret, self).selected()
		
		self.target = MOUSE
		
	def deselected(self):
		super(MouseTurret, self).deselected()
		
		self.target = None

class ButtonTurret(MouseTurret):
	bullet = Rail
	range = 500
	
	def __init__(self, game, socket, angle=0):
		super(ButtonTurret, self).__init__(game, socket, angle)
		self.button = settings.controls.railgun
	
	def update(self):	
		if self.target:
			self._target()
	
	def activate(self, button):
		if self.target_locked:
			self._shoot()
		
	def deactivate(self, button):
		pass
		
	def _shoot(self):
		target_position = self.target.position
		target_distance = self.position.distance(target_position)
		
		if target_distance <= self.range:
			position = self.position + AngleVector(self.angle, target_distance)
			bullet = self.bullet(self.game, self.position, position)
			self.game.add_object(bullet)
		
	def draw(self, surface):
		x, y = self.position
		
		rotate = pygame.transform.rotozoom(self.graphic, math.degrees(-self.angle), 1)
		width, height = rotate.get_size()
		
		surface.blit(rotate, (x - width // 2, y - height // 2))
	
	def event(self, event):
		if event.type == pygame.KEYDOWN and event.key == self.button:
			self.activate(event)
		elif event.type == pygame.KEYUP and event.key == self.button:
			self.deactivate(event)
	
	def crosshair(self, surface):
		if self.target_locked:
			position = self.target.position
			distance = self.position.distance(position)
			
			x, y = self.target.position
			
			if distance <= self.range:
				pygame.draw.line(surface, (0, 0, 255, 64), tuple(self.position), tuple(self.target.position), 1)
			
			range_ratio = distance / self.range
			alpha = min(int(255 * range_ratio), 255)
			
			arc_range = math.radians(20)
			
			pygame.draw.arc(surface, (0, 0, 255, alpha), self.range_rect, -self.angle - arc_range, -self.angle + arc_range)

class AutoTurret(Turret):
	def __init__(self, game, socket, angle=0):
		super(AutoTurret, self).__init__(game, socket, angle)
	
	def selected(self):
		super(AutoTurret, self).selected()
		
		self.target = MOUSE
		
	def deselected(self):
		super(AutoTurret, self).deselected()
		
		self.target = None
	
	def _target(self):
		target_distance = self.position.distance(self.target.position)
		hit_delay = target_distance / self.bullet.speed
		target_position = self.target.position + AngleVector(self.target.angle, (hit_delay * self.target.speed))
		
		self._turn_to_target(target_position)
	
	def _no_target(self):
		super(AutoTurret, self)._no_target()
		
		self.find_target()
	
	def _shoot(self):
		target_distance = self.position.distance(self.target.position)
		
		if target_distance <= self.range:
			hit_delay = target_distance / self.bullet.speed
			target_position = self.target.position + AngleVector(self.target.angle, (hit_delay * self.target.speed))
			target_distance = self.position.distance(target_position)
			
			position = self.position + AngleVector(self.angle, target_distance)
			bullet = Bullet(self.game, self.position, angles.random_point_in_circle((position, target_distance / 25)))
			self.game.add_object(bullet)
	
	def find_target(self): # noobic oobly
		def distance(unit):
			unit_x, unit_y = unit.position
			return (self.position - unit.position).magnitude()
		
		def weighted_distance(unit):
			target_angle = angles.angle_between_points(self.position, unit.position)
			turn_distance = min(angles.rotations_to_angle(self.angle, target_angle)) * (1 / self.turn_speed)
			
			return (distance(unit) / self.range) + turn_distance
		
		targets = self.game.collision_layers["units"].objects_within_rect(self.range_rect)
		targets = set(target for target in targets if distance(target) <= self.range)
		
		if targets:
			self.target = min(targets, key=lambda target: weighted_distance(target))

from graphics import graphics, request_replace
request_replace(Socket)
request_replace(Turret)
